// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/renderer/dom_storage/dom_storage_cached_area.h"

#include <limits>

#include "base/metrics/histogram_macros.h"
#include "base/time/time.h"
#include "content/common/dom_storage/dom_storage_map.h"
#include "content/renderer/dom_storage/dom_storage_proxy.h"

namespace content {

DOMStorageCachedArea::DOMStorageCachedArea(int64_t namespace_id,
    const GURL& origin,
    DOMStorageProxy* proxy)
    : ignore_all_mutations_(false)
    , namespace_id_(namespace_id)
    , origin_(origin)
    , proxy_(proxy)
    , weak_factory_(this)
{
}

DOMStorageCachedArea::~DOMStorageCachedArea() { }

unsigned DOMStorageCachedArea::GetLength(int connection_id)
{
    PrimeIfNeeded(connection_id);
    return map_->Length();
}

base::NullableString16 DOMStorageCachedArea::GetKey(int connection_id,
    unsigned index)
{
    PrimeIfNeeded(connection_id);
    return map_->Key(index);
}

base::NullableString16 DOMStorageCachedArea::GetItem(
    int connection_id,
    const base::string16& key)
{
    PrimeIfNeeded(connection_id);
    return map_->GetItem(key);
}

bool DOMStorageCachedArea::SetItem(int connection_id,
    const base::string16& key,
    const base::string16& value,
    const GURL& page_url)
{
    // A quick check to reject obviously overbudget items to avoid
    // the priming the cache.
    if ((key.length() + value.length()) * sizeof(base::char16) > kPerStorageAreaQuota)
        return false;

    PrimeIfNeeded(connection_id);
    base::NullableString16 unused;
    if (!map_->SetItem(key, value, &unused))
        return false;

    // Ignore mutations to 'key' until OnSetItemComplete.
    ignore_key_mutations_[key]++;
    proxy_->SetItem(
        connection_id, key, value, page_url,
        base::Bind(&DOMStorageCachedArea::OnSetItemComplete,
            weak_factory_.GetWeakPtr(), key));
    return true;
}

void DOMStorageCachedArea::RemoveItem(int connection_id,
    const base::string16& key,
    const GURL& page_url)
{
    PrimeIfNeeded(connection_id);
    base::string16 unused;
    if (!map_->RemoveItem(key, &unused))
        return;

    // Ignore mutations to 'key' until OnRemoveItemComplete.
    ignore_key_mutations_[key]++;
    proxy_->RemoveItem(
        connection_id, key, page_url,
        base::Bind(&DOMStorageCachedArea::OnRemoveItemComplete,
            weak_factory_.GetWeakPtr(), key));
}

void DOMStorageCachedArea::Clear(int connection_id, const GURL& page_url)
{
    // No need to prime the cache in this case.
    Reset();
    map_ = new DOMStorageMap(kPerStorageAreaQuota);

    // Ignore all mutations until OnClearComplete time.
    ignore_all_mutations_ = true;
    proxy_->ClearArea(connection_id,
        page_url,
        base::Bind(&DOMStorageCachedArea::OnClearComplete,
            weak_factory_.GetWeakPtr()));
}

void DOMStorageCachedArea::ApplyMutation(
    const base::NullableString16& key,
    const base::NullableString16& new_value)
{
    if (!map_.get() || ignore_all_mutations_)
        return;

    if (key.is_null()) {
        // It's a clear event.
        scoped_refptr<DOMStorageMap> old = map_;
        map_ = new DOMStorageMap(kPerStorageAreaQuota);

        // We have to retain local additions which happened after this
        // clear operation from another process.
        std::map<base::string16, int>::iterator iter = ignore_key_mutations_.begin();
        while (iter != ignore_key_mutations_.end()) {
            base::NullableString16 value = old->GetItem(iter->first);
            if (!value.is_null()) {
                base::NullableString16 unused;
                map_->SetItem(iter->first, value.string(), &unused);
            }
            ++iter;
        }
        return;
    }

    // We have to retain local changes.
    if (should_ignore_key_mutation(key.string()))
        return;

    if (new_value.is_null()) {
        // It's a remove item event.
        base::string16 unused;
        map_->RemoveItem(key.string(), &unused);
        return;
    }

    // It's a set item event.
    // We turn off quota checking here to accomodate the over budget
    // allowance that's provided in the browser process.
    base::NullableString16 unused;
    map_->set_quota(std::numeric_limits<int32_t>::max());
    map_->SetItem(key.string(), new_value.string(), &unused);
    map_->set_quota(kPerStorageAreaQuota);
}

void DOMStorageCachedArea::Prime(int connection_id)
{
    DCHECK(!map_.get());

    // The LoadArea method is actually synchronous, but we have to
    // wait for an asyncly delivered message to know when incoming
    // mutation events should be applied. Our valuemap is plucked
    // from ipc stream out of order, mutations in front if it need
    // to be ignored.

    // Ignore all mutations until OnLoadComplete time.
    ignore_all_mutations_ = true;
    DOMStorageValuesMap values;
    base::TimeTicks before = base::TimeTicks::Now();
    proxy_->LoadArea(connection_id,
        &values,
        base::Bind(&DOMStorageCachedArea::OnLoadComplete,
            weak_factory_.GetWeakPtr()));
    base::TimeDelta time_to_prime = base::TimeTicks::Now() - before;
    // Keeping this histogram named the same (without the ForRenderer suffix)
    // to maintain histogram continuity.
    UMA_HISTOGRAM_TIMES("LocalStorage.TimeToPrimeLocalStorage",
        time_to_prime);
    map_ = new DOMStorageMap(kPerStorageAreaQuota);
    map_->SwapValues(&values);

    size_t local_storage_size_kb = map_->bytes_used() / 1024;
    // Track localStorage size, from 0-6MB. Note that the maximum size should be
    // 5MB, but we add some slop since we want to make sure the max size is always
    // above what we see in practice, since histograms can't change.
    UMA_HISTOGRAM_CUSTOM_COUNTS("LocalStorage.RendererLocalStorageSizeInKB",
        local_storage_size_kb,
        1, 6 * 1024, 50);
    if (local_storage_size_kb < 100) {
        UMA_HISTOGRAM_TIMES(
            "LocalStorage.RendererTimeToPrimeLocalStorageUnder100KB",
            time_to_prime);
    } else if (local_storage_size_kb < 1000) {
        UMA_HISTOGRAM_TIMES(
            "LocalStorage.RendererTimeToPrimeLocalStorage100KBTo1MB",
            time_to_prime);
    } else {
        UMA_HISTOGRAM_TIMES(
            "LocalStorage.RendererTimeToPrimeLocalStorage1MBTo5MB",
            time_to_prime);
    }
}

void DOMStorageCachedArea::Reset()
{
    map_ = NULL;
    weak_factory_.InvalidateWeakPtrs();
    ignore_key_mutations_.clear();
    ignore_all_mutations_ = false;
}

void DOMStorageCachedArea::OnLoadComplete(bool success)
{
    DCHECK(success);
    DCHECK(ignore_all_mutations_);
    ignore_all_mutations_ = false;
}

void DOMStorageCachedArea::OnSetItemComplete(const base::string16& key,
    bool success)
{
    if (!success) {
        Reset();
        return;
    }
    std::map<base::string16, int>::iterator found = ignore_key_mutations_.find(key);
    DCHECK(found != ignore_key_mutations_.end());
    if (--found->second == 0)
        ignore_key_mutations_.erase(found);
}

void DOMStorageCachedArea::OnRemoveItemComplete(const base::string16& key,
    bool success)
{
    DCHECK(success);
    std::map<base::string16, int>::iterator found = ignore_key_mutations_.find(key);
    DCHECK(found != ignore_key_mutations_.end());
    if (--found->second == 0)
        ignore_key_mutations_.erase(found);
}

void DOMStorageCachedArea::OnClearComplete(bool success)
{
    DCHECK(success);
    DCHECK(ignore_all_mutations_);
    ignore_all_mutations_ = false;
}

} // namespace content
